package org.acghub.mtdb.core.log;

import org.acghub.mtdb.core.*;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.stream.Stream;

public abstract class LogAbstractCollection<E> extends AbstractCollection<E> implements Collection<E>, Verifyable {

    protected LogKey logkey;

    protected LogAbstractCollection<E> root;

    protected Collection<E> wrapped;

    protected Runnable verify;

    public LogAbstractCollection(LogKey logkey, Collection<E> wrapped) {
        this.logkey = logkey;
        this.wrapped = wrapped;
    }

    //for sublist subset
    protected LogAbstractCollection(LogAbstractCollection<E> root, Collection<E> wrapped) {
        this.root = root;
        this.wrapped = wrapped;
    }

    @Override
    public Runnable getVerify() {
        return verify;
    }

    @Override
    public void setVerify(Runnable verify) {
        this.verify = verify;
    }

    @Override
    public void verify() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Iterator<E> iterator() {
        return new LogIte(wrapped().iterator());
    }

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return wrapped().toArray(a);
    }

    @Override
    public boolean add(E e) {
        onChange();
        if (wrapped.add(e)) {
            onAdd(e);
            return true;
        }
        return false;
    }

    @Override
    public boolean remove(Object o) {
        onChange();
        if (wrapped().remove(o)) {
            onRemove((E) o);
            return true;
        }
        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return wrapped().containsAll(c);
    }

    @Override
    public int size() {
        return wrapped().size();
    }

    @Override
    public boolean isEmpty() {
        return wrapped().isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return wrapped().contains(o);
    }

    @Override
    public <T> T[] toArray(IntFunction<T[]> generator) {
        return wrapped().toArray(generator);
    }

    @Override
    public Spliterator<E> spliterator() {
        return wrapped().spliterator();
    }

    @Override
    public Stream<E> stream() {
        return wrapped().stream();
    }

    @Override
    public Stream<E> parallelStream() {
        return wrapped().parallelStream();
    }

    @Override
    public void forEach(Consumer<? super E> action) {
        wrapped().forEach(action);
    }

    protected <C extends Collection<E>> C wrapped() {
        return (C) wrapped;
    }

    protected final CollectionLog log() {
        SavePoint sp = Transaction.currentSavepoint();
        CollectionLog log = sp.get(logkey);
        if (null == log) {
            log = createLog();
            log.setVerify(verify);
            sp.add(logkey, log);
        }
        return log;
    }


    protected final void onChange() {
        if (root != null) {
            root.onChange();
        } else {
            log().onChange();
        }
    }

    protected final void onAdd(E e) {
        if (root != null) {
            root.onAdd(e);
        } else {
            log().onAdd(e);
        }
    }

    protected final void onAddAll(Collection<? extends E> c) {
        if (root != null) {
            root.onAddAll(c);
        } else {
            log().onAddAll(c);
        }
    }

    protected final void onRemove(E e) {
        if (root != null) {
            root.onRemove(e);
        } else {
            log().onRemove(e);
        }
    }

    protected CollectionLog createLog() {
        return new DefaultCollectionLog();
    }

    protected abstract class CollectionLog implements Log, Note, Verifyable {

        protected Runnable verify;

        @Override
        public Runnable getVerify() {
            return verify;
        }

        @Override
        public void setVerify(Runnable verify) {
            this.verify = verify;
        }

        protected void onChange() {
            verify();
        }

        protected abstract void onRemove(E e);

        protected abstract void onAdd(E e);

        protected void onAddAll(Collection<? extends E> c) {
            for (E e : c) {
                onAdd(e);
            }
        }

    }

    public final class DefaultCollectionLog extends CollectionLog {

        // copy on write
        private List<E> cow;

        @Override
        public void commit() {
            LogNotify.notify(logkey, this);
            cow = null;
        }

        @Override
        public void rollback() {
            if (cow != null) {
                wrapped().clear();
                wrapped().addAll(cow);
            }
            cow = null;
        }

        @Override
        protected void onChange() {
            super.onChange();
            if (cow == null) {
                cow = new ArrayList<>(wrapped());
            }
        }

        @Override
        protected void onRemove(E e) {
            Logs.link(e, null, null);
        }

        @Override
        protected void onAdd(E e) {
            Logs.link(e, logkey.getBean(), logkey.getVarname());
        }

        @Override
        protected void onAddAll(Collection<? extends E> c) {
            for (E e : c) {
                Logs.link(e, logkey.getBean(), logkey.getVarname());
            }
        }

        @Override
        public String toString() {
            return new StringJoiner(", ", DefaultCollectionLog.class.getSimpleName() + "[", "]")
                    .add("cow=" + cow)
                    .toString();
        }
    }

    protected class LogIte implements Iterator<E> {

        protected final Iterator<E> ite;

        protected E current;

        public LogIte(Iterator<E> iterator) {
            ite = iterator;
        }

        @Override
        public boolean hasNext() {
            return ite.hasNext();
        }

        @Override
        public E next() {
            return current = ite.next();
        }

        @Override
        public void remove() {
            onChange();
            ite.remove();
            onRemove(current);
        }
    }
}
